﻿
Imports System.Threading
Imports std
Imports storoomc
Imports storcore

Partial Public Class nodes_redundance2
    Inherits nodes_redundance

    Public Shared ReadOnly defaultTimeStamp As Int64 = minInt64
    'to save memory, just let the background syncer read the data again
    Private syncqueue As setqueue(Of String) = Nothing
    Private syncerTimer3Index As Guid = Guid.Empty
    Private fakerequestThread As Thread = Nothing
    Private fakerequestAre As AutoResetEvent = Nothing
    Private verifyRate As Int16 = 50

    Private MustInherit Class tsdata
        Implements IComparable(Of tsdata), IComparable
        Public ts As Int64 = defaultTimeStamp
        Public MustOverride Function size() As Int64

        Public Function CompareTo(ByVal obj As Object) As Integer Implements IComparable.CompareTo
            Return CompareTo(convertor.cast(Of tsdata)(obj))
        End Function

        'though compare can handle me is nothing / other is nothing, and some other cases, but i think this way is safer
        Public Function CompareTo(ByVal other As tsdata) As Integer Implements IComparable(Of tsdata).CompareTo
            'as the defaultTimeStamp is minInt64 and hasnoTimeStamp is minInt32,
            'and all the other possible ts is possitive, so just compare the int64 is enough
            If Me Is Nothing AndAlso other Is Nothing Then
                Return 0
            ElseIf Me Is Nothing Then
                Return -1
            ElseIf other Is Nothing Then
                Return 1
            Else
                If ts < other.ts Then
                    Return -1
                ElseIf ts > other.ts Then
                    Return 1
                Else
                    If size() < other.size() Then
                        Return -1
                    ElseIf size() > other.size() Then
                        Return 1
                    Else
                        Return 0
                    End If
                End If
            End If
        End Function
    End Class

    'do not make tsdata1 ICloneable, so we can save a pointer here
    Private Class tsdata1
        Inherits tsdata
        Implements IComparable(Of tsdata), IComparable, IComparable(Of tsdata1), IComparable(Of tsdata2)
        Public buff As bytes = Nothing

        Public Sub New()
        End Sub

        Public Sub New(ByVal ts As Int64, ByVal buff As bytes)
            Me.ts = ts
            Me.buff = buff
        End Sub

        Public Sub New(ByVal buff As bytes, ByVal ts As Int64)
            Me.New(ts, buff)
        End Sub

        Public NotOverridable Overrides Function size() As Int64
            If buff Is Nothing Then
                Return 0
            Else
                Return buff.size()
            End If
        End Function

        Public Overloads Function CompareTo(ByVal other As tsdata1) As Integer _
                                                                    Implements IComparable(Of tsdata1).CompareTo
            Return MyBase.CompareTo(convertor.cast(Of tsdata)(other))
        End Function

        Public Overloads Function CompareTo(ByVal other As tsdata2) As Integer _
                                                                    Implements IComparable(Of tsdata2).CompareTo
            Return MyBase.CompareTo(convertor.cast(Of tsdata)(other))
        End Function
    End Class

    'a faked data with only buffsize, instead of buff
    Private Class tsdata2
        Inherits tsdata
        Implements IComparable, IComparable(Of tsdata), IComparable(Of tsdata1), IComparable(Of tsdata2)
        Public _size As Int64 = 0

        Public Sub New()
        End Sub

        Public Sub New(ByVal size As Int64, ByVal ts As Int64)
            Me._size = size
            Me.ts = ts
        End Sub

        Public NotOverridable Overrides Function size() As Int64
            Return _size
        End Function

        Public Overloads Function CompareTo(ByVal other As tsdata1) As Integer _
                                                                    Implements IComparable(Of tsdata1).CompareTo
            Return MyBase.CompareTo(convertor.cast(Of tsdata)(other))
        End Function

        Public Overloads Function CompareTo(ByVal other As tsdata2) As Integer _
                                                                    Implements IComparable(Of tsdata2).CompareTo
            Return MyBase.CompareTo(convertor.cast(Of tsdata)(other))
        End Function
    End Class

    'syncerStore needs thread protected
    'cannot confirm whether timer2/timer3 always make call in ONE thread.
    Private syncerStore As tsdata1 = Nothing

    Protected NotOverridable Overrides Function slicehealth(ByVal slice As storageInterface) As Boolean
        'do nothing when slicehealth, though do clear slicehealthHandler.
        Return True
    End Function

    Private Function syncerRead(ByVal key As String) As Boolean
        Return threadModel().multiOper(d(Function(ByRef s As storageInterface, ByRef this As tsdata1) As ternary
                                             If Not storageInterfaceRead(s, key, this) Then
                                                 this = Nothing
                                             End If
                                             Return ternary.true
                                         End Function),
                                       Function(ByRef this As tsdata1) As Boolean
                                           If Not this Is Nothing AndAlso compare(syncerStore, this) < 0 Then
                                               syncerStore.buff = this.buff
                                               syncerStore.ts = this.ts
                                           End If
                                           Return True
                                       End Function, slices).true_()
    End Function

    Private Function syncerModify(ByVal key As String) As Boolean
        Return threadModel().multiOper(
            d_strict(Function(ByRef s As storageInterface) As ternary
                         Dim thists As Int64 = defaultTimeStamp
                         Dim size As UInt64 = 0
                         If Not storageInterfaceReadTimeStamp(s, key, thists) OrElse _
                            Not s.sizeof(datakey(key), size) OrElse _
                            compare(New tsdata2(size, thists), syncerStore) < 0 Then
                             If storageInterfaceModify(s, key, syncerStore) Then
                                 Return ternary.true
                             Else
                                 raiseError("cannot modify slice in syncerModify, slice " + _
                                            showslice(s) + ", key " + key, _
                                            errorHandle.errorType.warning)
                                 Return ternary.false
                             End If
                         ElseIf compare(New tsdata2(size, thists), syncerStore) > 0 Then
                             raiseError("got a timestamp or size larger than what syncerRead got, " +
                                        "assume has been changed already. key " + key,
                                        errorHandle.errorType.warning)
                             Return ternary.unknown
                         Else
                             Return ternary.true
                         End If
                     End Function),
            slices).true_()
    End Function

    Private Sub insertSyncQueue(ByVal key As String)
        SyncLock syncqueue
            syncqueue.push(key)
        End SyncLock
    End Sub

    Private Function clearSyncQueue() As Boolean
        SyncLock syncqueue
            syncqueue.clear()
        End SyncLock
        Return True
    End Function

    Private Sub syncer()
        assert(Not syncerStore Is Nothing, "syncerStore has not been initialized before syncer call starts.")
        Dim k As String = Nothing
        SyncLock syncqueue
            If Not syncqueue.empty() Then
                k = syncqueue.front()
                syncqueue.pop()
            End If
        End SyncLock

        If Not isemptystring(k) Then
            fakerequestAre.Set()
            If isdebugmode() Then
                raiseError("sync key " + k)
            End If
            'cannot confirm whether timer3 makes two calls with different threads at a certain time
            SyncLock syncerStore
                syncerStore.buff = Nothing
                syncerStore.ts = defaultTimeStamp
                If Not syncerRead(k) Then
                    raiseError("cannot finish syncerRead.", errorHandle.errorType.exclamation)
                    If seek(k) <> ternary.false Then
                        insertSyncQueue(k)
                    End If
                ElseIf Not syncerStore.buff = Nothing AndAlso syncerStore.ts <> defaultTimeStamp Then
                    If Not syncerModify(k) Then
                        raiseError("cannot syncerModify for key " + k, errorHandle.errorType.warning)
                        'let's try again later if cannot modify
                        insertSyncQueue(k)
                    End If
                Else
                    raiseError("cannot read data for key " + k + ", assume the data has been erased already.",
                               errorHandle.errorType.warning)
                End If
            End SyncLock
        End If
    End Sub

    Private Sub fakerequest()
        Dim l As vector(Of String) = Nothing
        Dim l2 As vector(Of String) = Nothing
        l = New vector(Of String)()
        l2 = New vector(Of String)()
        While True
            fakerequestAre.WaitOne()
            raiseError("begin fakerequest.")
            l.clear()
            For i As Int64 = 0 To slices.size() - 1
                l2.clear()
                If storageInterfaceList(slices(i), l2) Then
                    l.push_back(l2)
                End If
            Next
            If l.size() > 0 Then
                Dim i As Int64 = 0
                While i < l.size()
                    If l(i) Is Nothing Then
                        raiseError("found an invalid empty key when fakerequest.", errorHandle.errorType.warning)
                    Else
                        sizeof_verify(l(i), Nothing)
                    End If
                    i += 1
                    If jobcount() < 10 Then
                        sleep(200)
                    Else
                        sleep(1000)
                    End If
                End While
            Else
                raiseError("cannot list.", errorHandle.errorType.exclamation)
            End If
            raiseError("end fakerequest.")
        End While
    End Sub

    Private Function wm_redundancy(ByVal key As String, ByVal buff As bytes, ByVal wm As wmDelegate3) As Boolean
        assert(Not wm Is Nothing)
        Dim ts As tsdata1 = Nothing
        ts = New tsdata1(buff, Now.Ticks())
        Dim written As Boolean = False
        Return threadModel().multiOper(d(Function(ByRef s As storageInterface, ByRef this As Boolean) As ternary
                                             this = wm(s, key, ts)
                                             Return ternary.true
                                         End Function),
                                       Function(ByRef rst As ternary, ByRef this As Boolean) As Boolean
                                           If rst <> ternary.true Then
                                               insertSyncQueue(key)
                                           ElseIf this Then
                                               written = True
                                           Else
                                               insertSyncQueue(key)
                                           End If
                                           Return True
                                       End Function, slices).true_() AndAlso written
    End Function

    Protected NotOverridable Overrides Function write_redundancy(ByVal key As String, ByVal buff As bytes) As Boolean
        Return wm_redundancy(key, buff, AddressOf storageInterfaceWrite)
    End Function

    Protected NotOverridable Overrides Function modify_redundancy(ByVal key As String,
                                                                  ByVal buff As bytes) As Boolean
        Return wm_redundancy(key, buff, AddressOf storageInterfaceModify)
    End Function

    Private Function verify_random(Of T)(ByVal slices As vector(Of storageInterface), ByRef arg As T,
                                         ByVal verify As _do(Of T, ternary),
                                         ByVal random As _do(Of storageInterface, T, ternary)) As ternary
        assert(Not random Is Nothing)
        assert(Not slices Is Nothing)
        assert(slices.size() = 0 OrElse slices.size() = 1)
        If slices.size() = 0 Then
            Return ternary.false
        ElseIf Not verify Is Nothing AndAlso passVerifyRate() Then
            Return verify(arg)
        Else
            Dim rst As T = Nothing
            Dim rtn As ternary = Nothing
            rtn = threadModel().singleOper(Function() As ternary
                                               Return random(slices(0), rst)
                                           End Function)
            If rtn = ternary.true Then
                arg = rst
                Return ternary.true
            Else
                Return rtn
            End If
        End If
    End Function

    Private Function read_verify(ByVal key As String, ByRef buff As bytes) As Boolean
        Dim ts As tsdata1 = Nothing
        If threadModel().multiOper(d(Function(ByRef s As storageInterface, ByRef this As tsdata1) As ternary
                                         If Not storageInterfaceRead(s, key, this) Then
                                             this = Nothing
                                         End If
                                         Return ternary.true
                                     End Function),
                                       Function(ByRef this As tsdata1,
                                                ByRef total As tsdata1) As Boolean
                                           If Not this Is Nothing AndAlso
                                              this.ts <> defaultTimeStamp Then
                                               'no other before
                                               If total Is Nothing Then
                                                   total = New tsdata1(this.ts, this.buff)
                                               Else
                                                   Dim cmp As Int32 = 0
                                                   cmp = compare(total, this)
                                                   If cmp <> 0 Then
                                                       insertSyncQueue(key)
                                                       If cmp < 0 Then
                                                           total.ts = this.ts
                                                           total.buff = this.buff
                                                       End If
                                                   End If
                                               End If
                                           Else
                                               If total Is Nothing Then
                                                   total = New tsdata1(defaultTimeStamp, Nothing)
                                               ElseIf total.ts <> defaultTimeStamp Then
                                                   insertSyncQueue(key)
                                               End If
                                           End If

                                           Return True
                                       End Function,
                                       slices, ts).true_() AndAlso Not ts Is Nothing AndAlso Not ts.buff Is Nothing Then
            buff = ts.buff
            Return True
        Else
            Return False
        End If
    End Function

    Protected NotOverridable Overrides Function readImpl(ByVal key As String, ByRef buff As bytes, _
                                                         ByVal slices As vector(Of storageInterface)) As Boolean
        Return verify_random(slices, buff, Function(ByRef arg As bytes) As ternary
                                               Return read_verify(key, arg)
                                           End Function,
                                           Function(ByRef s As storageInterface, ByRef arg As bytes) As ternary
                                               Return storageInterfaceRead(s, key, arg, Nothing)
                                           End Function)
    End Function

    Private Function seek_verify(ByVal key As String) As Boolean
        Dim found As Boolean = False
        Dim first As Boolean = True
        Return threadModel().multiOper(d(Function(ByRef s As storageInterface, ByRef this As Boolean) As ternary
                                             this = storageInterfaceSeek(s, key)
                                             Return ternary.true
                                         End Function),
                                         Function(ByRef rst As ternary, ByRef this As Boolean) As Boolean
                                             If rst = ternary.true Then
                                                 If first Then
                                                     found = this
                                                     first = False
                                                 Else
                                                     If found <> this Then
                                                         insertSyncQueue(key)
                                                         If this Then
                                                             found = True
                                                         End If
                                                     End If
                                                 End If
                                             ElseIf found Then
                                                 insertSyncQueue(key)
                                             End If
                                             Return True
                                         End Function,
                                       slices).true_() AndAlso found
    End Function

    Protected NotOverridable Overrides Function seekImpl(ByVal key As String,
                                                         ByVal slices As vector(Of storageInterface)) As ternary
        Return verify_random(slices, True, Function(ByRef _NIU_ As Boolean) As ternary
                                               Return seek_verify(key)
                                           End Function,
                                           Function(ByRef s As storageInterface, ByRef _NIU_ As Boolean) As ternary
                                               Return storageInterfaceSeek(s, key)
                                           End Function)
    End Function

    Protected Overrides Function listImpl(ByVal l As vector(Of String),
                                          ByVal slices As vector(Of storageInterface)) As Boolean
        Return verify_random(slices, True, Nothing,
                             Function(ByRef s As storageInterface, ByRef _NIU_ As Boolean) As ternary
                                 Return storageInterfaceList(s, l)
                             End Function)
    End Function

    Private Function _delete(ByVal key As String) As Boolean
        'do not need to care about whether the delete operation is success or not,
        'since the data is not necessary any more.
        Dim deleted As Boolean = False
        Return threadModel().multiOper(d(Function(ByRef s As storageInterface) As ternary
                                             If storageInterfaceDelete(s, key) Then
                                                 deleted = True
                                             End If
                                             Return ternary.true
                                         End Function), slices).true_() AndAlso deleted
    End Function

    Protected NotOverridable Overrides Function deleteImpl(ByVal key As String, _
                                                           ByVal slices As vector(Of storageInterface)) As Boolean
        Return _delete(key)
    End Function

    Private Function sizeof_verify(ByVal key As String, ByRef size As UInt64) As Boolean
        Dim rst As pair(Of Boolean, UInt64) = Nothing
        If threadModel().multiOper(d(Function(ByRef s As storageInterface,
                                              ByRef this As pair(Of Boolean, UInt64)) As ternary
                                         Dim sz As UInt64 = 0
                                         Dim r As Boolean = False
                                         r = storageInterfaceSizeof(s, key, sz)
                                         this = make_pair(r, sz)
                                         Return ternary.true
                                     End Function),
                                   Function(ByRef rtn As ternary,
                                            ByRef this As pair(Of Boolean, UInt64),
                                            ByRef total As pair(Of Boolean, UInt64)) As Boolean
                                       If rtn = ternary.true AndAlso this.first Then
                                           If total Is Nothing Then
                                               total = make_pair(Of Boolean, UInt64)(True, this.second)
                                           Else
                                               If total.second <> this.second OrElse Not (total.first) Then
                                                   insertSyncQueue(key)
                                                   total.first = True
                                                   If total.second < this.second Then
                                                       total.second = this.second
                                                   End If
                                               End If
                                           End If
                                       Else
                                           If total Is Nothing Then
                                               total = make_pair(Of Boolean, UInt64)(False, 0)
                                           ElseIf total.first Then
                                               insertSyncQueue(key)
                                           End If
                                       End If

                                       Return True
                                   End Function, slices, rst).true_() AndAlso rst.first Then
            size = rst.second
            Return True
        Else
            Return False
        End If
    End Function

    Protected Overrides Function sizeofImpl(ByVal key As String, ByRef size As UInt64, _
                                            ByVal slices As vector(Of storageInterface)) As Boolean
        Return verify_random(slices, size, Function(ByRef arg As UInt64) As ternary
                                               Return sizeof_verify(key, arg)
                                           End Function,
                                           Function(ByRef s As storageInterface, ByRef arg As UInt64) As ternary
                                               Return storageInterfaceSizeof(s, key, arg)
                                           End Function)
    End Function

    Private Function passVerifyRate() As Boolean
        Return rnd(0, 100, True) < verifyRate
    End Function

    Public Overloads Sub startImpl()
        MyBase.startImpl(False)
        syncerTimer3Index = timer3.addcall(AddressOf syncer, 100)
        fakerequestThread = startThread(AddressOf fakerequest)
        sliceHealthHandle = Nothing
    End Sub

    Public NotOverridable Overrides Sub start()
        nodesStart(Me)
        verifyRate = config.parameter(configNaming.storoom.name,
                                      configNaming.storoom.redundance2_verifyRate,
                                      configNaming.storoom.defaultValue.redundance2_verifyRate)
        startImpl()
    End Sub

    Protected NotOverridable Overrides Function closeImpl(ByVal slices As vector(Of storageInterface)) As Boolean
        If syncerTimer3Index <> Guid.Empty Then
            debugassert(timer3.removecall(syncerTimer3Index), "cannot remove timer3 call")
            syncerTimer3Index = Guid.Empty
        End If
        If Not fakerequestThread Is Nothing Then
            fakerequestThread.Abort()
        End If
        Return MyBase.closeImpl(slices)
    End Function

    Protected Overrides Function keycountImpl(ByRef count As UInt64,
                                              ByVal slices As vector(Of storageInterface)) As Boolean
        Return verify_random(slices, count, Nothing, Function(ByRef s As storageInterface, ByRef c As UInt64) As ternary
                                                         Return storageInterfaceKeyCount(s, c)
                                                     End Function)
    End Function

    Protected NotOverridable Overrides Function retireImpl(ByVal slices As vector(Of storageInterface)) As Boolean
        Return MyBase.retireImpl(slices) AndAlso clearSyncQueue()
    End Function

    Public Sub New()
        syncqueue = New setqueue(Of String)()
        syncerStore = New tsdata1()
        fakerequestAre = New AutoResetEvent(False)
    End Sub

    Protected Overrides Sub Finalize()
        fakerequestAre.Close()
        MyBase.Finalize()
    End Sub
End Class
